﻿using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Text;

namespace DiscreteMath.Tests
{
    [TestClass]
    public class ContextFreeGrammars
    {
        EquivalenceTester tester;
        TestableContextFreeGrammar[] grammars;
        ISet<char> alphabet;

        [TestInitialize]
        public void PrepareTests()
        {
            alphabet = new HashSet<char>() { 'a', 'b' };
            tester = new EquivalenceTester(TestConfigurationVariables.TestCount, new WordGenerator(alphabet.ToList()));
            grammars = ContextFreeGrammarFactory.Factory.GetAllTests().Cast<TestableContextFreeGrammar>().ToArray();
        }

        [TestMethod]
        public void Basic()
        {
            foreach (TestableContextFreeGrammar test in grammars)
            {
                tester.TestEquivalence(test.Device.GenerateWord, test.RecognitionTest, "Word generation of " + test.Name);
            }
        }

        [TestMethod]
        public void Union()
        {
            for (int i = 0; i < grammars.Length; i++)
            {
                for (int j = i; j < grammars.Length; j++)
                {
                    TestableContextFreeGrammar first = grammars[i];
                    TestableContextFreeGrammar second = grammars[j];
                    ContextFreeGrammar union = first.Device.Union(second.Device);
                    Func<string, bool> test = PredicateCombiner.CombineUnion(first.RecognitionTest, second.RecognitionTest);

                    tester.TestEquivalence(union.GenerateWord, test, string.Format("Union of {0} and {1}", first.Name, second.Name));
                }
            }
        }

        [TestMethod]
        public void Concatenate()
        {
            for (int i = 0; i < grammars.Length; i++)
            {
                for (int j = 0; j < grammars.Length; j++)
                {
                    TestableContextFreeGrammar first = grammars[i];
                    TestableContextFreeGrammar second = grammars[j];
                    ContextFreeGrammar concatenation = first.Device.Concatenate(second.Device);
                    Func<string, bool> test = PredicateCombiner.CombineConcatenation(first.RecognitionTest, second.RecognitionTest);

                    tester.TestEquivalence(concatenation.GenerateWord, test, string.Format("Concatenation of {0} and {1}", first.Name, second.Name));
                }
            }
        }

        [TestMethod]
        public void Klenee()
        {
            throw new NotImplementedException();
        }

        [TestMethod]
        public void FromToRegex()
        {
            for (int i = 0; i < 10; i++)
			{
			    string regex = GenerateRegex();
                NondeterministicAutomaton automaton = NondeterministicAutomaton.FromRegex(regex);
                ContextFreeGrammar grammar = RegularGrammar.FromRegex(regex);

                tester.TestEquivalence(grammar, automaton, "Grammar and automaton for " + regex); 
			}
        }

        public static string GenerateRegex()
        {
            char leftBracket = '(';
            char rightBracket = ')';
            List<char> alphabet = new List<char>() { 'a', 'b', 'c' };
            List<char> operators = new List<char>() { 
                SpecialSigns.ConcatenationSign, SpecialSigns.UnionSign, SpecialSigns.KleeneeStarSign, leftBracket };

            StringBuilder builder = new StringBuilder();
            Random rng = new Random();
            int maxLength = (int)(10 * rng.NextDouble() + 5);
            int leftBracketsCount = 0;
            for (int i = 0; i < maxLength; i++)
            {
                char token = rng.NextDouble() < 1 / alphabet.Count ? leftBracket : alphabet[rng.Next(alphabet.Count)];
                builder.Append(token);
                if (token == leftBracket)
                {
                    leftBracketsCount++;
                    token = alphabet[rng.Next(alphabet.Count)];
                    builder.Append(token);
                }

                token = leftBracketsCount > 0 && rng.NextDouble() < 1 / operators.Count ? rightBracket : operators[rng.Next(operators.Count)];
                builder.Append(token);
                if (token == rightBracket)
                {
                    leftBracketsCount--;
                }
                if (token == leftBracket)
                {
                    leftBracketsCount++;
                }
            }

            if (operators.Contains(builder[builder.Length - 1]))
            {
                builder.Append(alphabet[rng.Next(alphabet.Count)]);
            }
            builder.Append(new string(rightBracket, leftBracketsCount));

            return builder.ToString()
                .Replace(SpecialSigns.ConcatenationSign.ToString(), string.Empty);
        }
    }
}
